#!/usr/bin/env python
# coding=utf-8
"""
    secchecksuite/config.py
    config.py for automatic config information for plugin
    :CopyRight(c) 2019 Inspur Electronic Information Industry Co.,Ltd
"""
import re
import os
import sys
import collections
import functools
from shutil import copyfile
import aes_cbc
import getpass
import utils
import check
import constant as const


class SecurityConfig:
    def __init__(self, python, key, config_info):
        self.python = python
        self.key = key
        self.config_info = config_info
        self.suffix = '.cbb_security'

    def config(self):
        while True:
            try:
                self.continue_config()
                params = dict()
                params['module'] = self.get_module()
                params['config_path'] = self.get_config_path(params)
                params['user_info'] = self.get_user_info()
                command = self.get_command()
                if command != '-1':
                    params['command'] = command
                cmd_time = self.get_cmd_time()
                if cmd_time != '-1':
                    params['cmd_time'] = cmd_time
                plugin_timeout = self.get_plugin_timeout()
                if plugin_timeout != '-1':
                    params['plugin_timeout'] = plugin_timeout
                version = self.get_version()
                if version != '-1':
                    params['version'] = version
                if check.check_not_none_dict(params):
                    self.modify_configure_file(params)
                    print('Config Complete!\n\n')
            except (SystemExit, BaseException) as err:
                if check.check_normal_exit(err):
                    utils.program_exit(sys.argv[0], 0)
                print('Occur some error in config progress: {}'.format(err))
                utils.program_exit(sys.argv[0], -1)

    def continue_config(self):
        begin = self.python.input_info("Let's Start Config Now (q exit config, other continue config):")
        begin = begin.strip().lower() if begin else None
        if begin == 'q':
            utils.program_exit(sys.argv[0], 0)

    def modify_configure_file(self, params):
        """
        modify and save new plugins data
        Args:
            params: configure information get from terminal
        """
        module_config = params.get('module', None)
        entry = module_config.get('entry') if module_config else None
        module_id = module_config.get('module') if module_config else None
        entry_points = self.config_info.get('entrypoints', None)
        for entry_point in self.config_info.get('entrypoints', None):
            if entry and entry != 'all' and entry != entry_point:
                continue
            module_list = entry_points.get(entry_point, None)
            self.handle_plugins_data(module_list, module_id, params, entry_point)

    def handle_plugins_data(self, module_list, module_id, params, entry_point):
        """
        handle plugins data
        Args:
            module_list: entryPoints
            module_id: module of plugin
            params: configure information get from terminal
            entry_point: entry_point
        """
        for plugin in module_list:
            enter_point = plugin.get('plugin', None)
            if not enter_point or enter_point == '':
                continue
            plugins_data = utils.load_config_file(enter_point)
            if not check.check_not_none_dict(plugins_data):
                continue
            plugin_data_id = plugins_data.get('id', None)
            if module_id and module_id != 'all' and module_id not in plugin_data_id:
                continue
            print("\nStart modify plugins data in file {}.".format(enter_point))
            if plugin_data_id in const.THIRD_PLUGIN_DATA_ID:
                plugins_data = self.modify_system_plugin(plugins_data, params)
            else:
                plugins_data = self.modify_scrip_plugin(plugins_data, params, entry_point)
            self.rewrite_plugins_data(plugins_data, enter_point)

    def copy_ordered_info(self, plugin_data):
        if not isinstance(plugin_data, dict) or not plugin_data:
            return plugin_data
        template_keys = ['id', 'description', 'plugintype', 'source', 'user_info', "plugins"]

        def comp_keys(key1, key2):
            id1 = template_keys.index(key1) if key1 in template_keys else -1
            id2 = template_keys.index(key2) if key2 in template_keys else -1
            if id1 >= 0 and id2 >= 0:
                result = 1 if id1 >= id2 else -1
            else:
                result = 1 if id1 <= id2 else -1
            return result

        sorted_keys = sorted(plugin_data.keys(), key=functools.cmp_to_key(comp_keys))
        sorted_plugin_data = collections.OrderedDict()
        for item in sorted_keys:
            sorted_plugin_data[item] = plugin_data[item]
        return sorted_plugin_data

    def rewrite_plugins_data(self, plugins_data, enter_point):
        """
        rewrite plugins data to json file
        Args:
            plugins_data: modified plugins data
            enter_point: path of json file
        """
        if not self.backup_configure_file(enter_point):
            return False
        print("\nStart save configure file {}".format(enter_point))
        result = self.python.save_config_file(enter_point, plugins_data)
        return self.cleanup_configure_env(enter_point, result)

    def backup_configure_file(self, enter_point):
        print("\nStart backup configure file {}.".format(enter_point))
        new_path = enter_point + self.suffix
        try:
            if os.path.exists(new_path) and os.path.isfile(new_path):
                os.remove(new_path)
            copyfile(enter_point, new_path)
            print("Backup Success.\n")
            return True
        except IOError as err:
            print("Backup Fail. Occur some errors: {}\n".format(err))
            return False

    def cleanup_configure_env(self, enter_point, result):
        print("Start cleanup configure file {}.\n".format(enter_point))
        new_path = enter_point + self.suffix
        if not os.path.exists(new_path) or not os.path.isfile(new_path):
            print("Config file {} doesn't exist.\n".format(new_path))
            return False
        try:
            if result:
                os.remove(new_path)
                print("Remove Backup File Success!\n")
            else:
                copyfile(new_path, enter_point)
                print("Rollback Backup File Success!\n")
            return True
        except IOError as err:
            print("Cleanup Fail! Occur some errors: {}\n".format(err))
            return False

    def modify_system_plugin(self, plugin_data, params):
        """
        handle plugins data which plugintype is exe and id is System
        Args:
            plugin_data: plugins data information
            params: configure information get from terminal
        Returns:
            plugins_data
        """
        config_user_info = params.get('user_info', None)
        plugins = plugin_data.get('plugins', None)
        for plugin in plugins:
            str_remote = ''
            if 'version' in params:
                plugin['version'] = params.get('version', '')
            if 'plugin_timeout' in params:
                plugin['plugin_timeout'] = params.get('plugin_timeout', 10)
            if not check.check_not_none_dict(config_user_info):
                continue
            plugin_config = plugin.get('plugin_config', None)
            if 'encryption' in config_user_info:
                plugin['encryption'] = config_user_info.get('encryption')
            if 'ip' in config_user_info:
                str_remote = str_remote + ' -i {}'.format(config_user_info.get('ip'))
                plugin_config = self.split_plugin_config('-i', plugin_config)
            if 'system_user' in config_user_info:
                str_remote = str_remote + ' -u {}'.format(config_user_info.get('system_user'))
                plugin_config = self.split_plugin_config('-u', plugin_config)
            if 'raw_pwd' in config_user_info:
                str_remote = str_remote + ' -p {}'.format(config_user_info.get('raw_pwd'))
                plugin_config = self.split_plugin_config('-p', plugin_config)
            encryption = plugin.get('encryption')
            plugin['plugin_config'] = plugin_config + str_remote
            if encryption and str(encryption) == '1':
                plugin['plugin_config'] = self.python.str(aes_cbc.aes_encrypt(self.key, plugin.get('plugin_config')))
        plugin_data = self.copy_ordered_info(plugin_data)
        return plugin_data

    def split_plugin_config(self, loc, plugin_config):
        """
        split plugin config information with ' ', then regroup new values according to terminal info
        Args:
            loc: -i -p or -u
            plugin_config: plugin config
        Returns:
            new plugin config
        """
        if not plugin_config or loc in plugin_config:
            return plugin_config
        config_list = list(filter(None, plugin_config.split()))
        new_list = []
        for i in range(len(config_list)):
            if loc == config_list[i]:
                continue
            if (i + 1 < len(config_list) - 1) and \
                    (config_list[i - 1] == loc) and \
                    ('-' not in config_list[i]) and \
                    ('-' in config_list[i + 1]):
                continue
            new_list.append(config_list[i])
        return ' '.join(new_list)

    def modify_scrip_plugin(self, plugins_data, params, entry_point):
        """
        handle plugins data which plugintype is script
        Args:
            plugins_data: plugins data information
            params: configure information get from terminal
            entry_point: entry_point
        Returns:
            plugins_data
        """
        config_user_info = params.get('user_info', None)
        plugin_user_info = plugins_data.get('user_info', None)
        if config_user_info:
            for item in ['system_user', 'system_pwd', 'ip', 'encryption']:
                if item in config_user_info:
                    plugin_user_info[item] = config_user_info.get(item)
            plugins_data['user_info'] = plugin_user_info
        plugins = plugins_data.get('plugins', None)
        for item in plugins:
            plugin_config = item.get('plugin_config', None)
            if 'command' in params:
                plugin_config['command'] = params.get('command', 1)
            if 'cmd_time' in params:
                plugin_config['cmd_time'] = params.get('cmd_time', 3)
            if entry_point.lower() == 'k8s' and params.get('config_path', None):
                if plugins_data.get('id') not in ['master', 'node']:
                    plugin_config['config_path'] = params.get('config_path')
            if 'version' in params:
                item['version'] = params.get('version', '')
            item['plugin_config'] = plugin_config
        plugins_data['plugins'] = plugins
        plugins_data = self.copy_ordered_info(plugins_data)
        return plugins_data

    def get_module(self, config=True):
        """
        get module info from terminal
        Returns:
            module_list
        """
        import readline
        if config:
            print('\nStart Config module, which conclude entryPoints and module in entryPoints!')
            input_info = const.MODULE_INFO
        else:
            print('\nPlease input which module you want to execute!')
            input_info = const.EXCUTE_INFO
        while True:
            config_module = self.python.input_info(input_info)
            module_dict, flag = self.complete_valid_module(config_module)
            if not flag:
                continue
            return module_dict

    def complete_valid_module(self, config_module):
        config_module = config_module.strip() if config_module else None
        if not config_module:
            return None, True
        module_list = list(filter(None, config_module.split(',')))
        if not module_list or not len(module_list):
            return None, True
        module_dict = dict()
        module_dict['entry'], entry_flag = self.valid_entry(module_list)
        if not entry_flag:
            return None, False
        module_dict, module_flag = self.valid_module(module_list, module_dict)
        if not module_flag:
            return None, False
        return module_dict, True

    def valid_module(self, module_list, module_dict):
        """
        validate module
        Args:
            module_list: the module info get from internal
            module_dict: effective entryPoints value
        Returns:
            module_list
            Boolean
        """
        if len(module_list) < 2:
            return module_dict, True
        module_id = module_list[1].strip()
        if not module_id or module_id.lower() == 'all':
            return module_dict, True
        entry = module_dict.get('entry', None)
        exist = False
        entry_points = self.config_info.get('entrypoints', None)
        for entry_point in entry_points:
            if entry and entry != 'all' and entry != entry_point:
                continue
            module_list = entry_points.get(entry_point, None)
            for plugin in module_list:
                enter_point = plugin.get('plugin', None)
                if not enter_point or enter_point == '':
                    continue
                plugins_data = utils.load_config_file(enter_point)
                if module_id in plugins_data.get('id', None):
                    exist = True
                    break
        if not exist:
            print('value module does not exist')
            return module_dict, False
        module_dict['module'] = module_id
        return module_dict, True

    def valid_entry(self, module_list):
        """
        validate entry
        Args:
            module_list: the module info get from internal
        Returns:
            entry
            Boolean
        """
        entry = module_list[0].strip()
        if not entry:
            return entry, True
        entry_points = self.config_info.get('entrypoints', None)
        if entry not in entry_points and entry.lower() != 'all':
            print('value entryPoints does not exist in entrypoints!\n')
            return entry, False
        return entry, True

    def get_config_path(self, params):
        module = params.get('module', None)
        if not module or not isinstance(module, dict):
            return None
        entry = module.get('entry', None)
        if entry.lower() != 'all' and entry.lower() != 'k8s':
            return None
        module = module.get('module', None)
        if module in ['master', 'node']:
            return None
        tip = "Do you want to config config_path for k8s? (y/n): "
        if self.get_confirm(tip) == '-1':
            return None
        while True:
            config_path = self.python.input_info(
                "Enter config_path (we do not config for master and node, because they have different config files):\n")
            config_path = config_path.strip() if config_path else None
            if config_path:
                return config_path

    def get_cmd_time(self):
        """
        get cmd_time from terminal
        Returns:
            cmd_time
        """
        print("\nStart Config cmd_time!")
        tip = "Do you want to config cmd_time (default cmd_time: 3s)? (y/n): "
        if self.get_confirm(tip) == '-1':
            return '-1'
        while True:
            cmd_time = self.python.input_info(
                "Enter cmd_time value in plugin (default cmd_time: 3s), cmd_time must be (0, 3600]:\n")
            cmd_time = cmd_time.strip() if cmd_time else None
            if not cmd_time:
                return 3
            regex = r'[0-9]+$'
            pattern = re.compile(regex)
            if pattern.match(cmd_time) and 0 < int(cmd_time) <= 3600:
                return cmd_time

    def get_confirm(self, tip):
        """
        get response from terminal
        Returns:
            flag: whether config special value
        """
        while True:
            confirm = self.python.input_info(tip)
            confirm = confirm.strip().lower() if confirm else None
            if not confirm or confirm == 'n':
                return '-1'
            if confirm == 'y':
                return True

    def get_ip(self):
        """
        get ip from terminal
        Returns:
            ip
        """
        print("\nStart Config IP!")
        tip = "Do you want to config Ip? (y/n): "
        if self.get_confirm(tip) == '-1':
            return '-1'
        while True:
            ip = self.python.input_info("Enter Ip:\n")
            ip = ip.strip() if ip else None
            if not ip:
                return ''
            if check.check_ip(ip):
                return ip

    def get_system_pwd(self):
        """
        get system password and system password encryption mark from terminal
        Returns:
            raw_pwd
            system_pwd
            system_pwd_encryption
        """
        print("\nStart Config system_pwd!")
        tip = "Do you want to config system_pwd? (y/n): "
        if self.get_confirm(tip) == '-1':
            return '-1', '', ''
        encryption = self.get_system_pwd_encryption()
        while True:
            system_pwd = getpass.getpass(prompt="Enter system_pwd:\n", stream=None)
            system_pwd = system_pwd.strip() if system_pwd else ''
            if not system_pwd or (system_pwd and '"' not in system_pwd):
                confirm = getpass.getpass("Enter again:\n")
                confirm = confirm.strip() if confirm else ''
                if system_pwd == confirm:
                    if confirm and encryption == 'y':
                        encryption_pwd = self.python.str(aes_cbc.aes_encrypt(self.key, system_pwd))
                        return system_pwd, encryption_pwd, encryption
                    else:
                        return system_pwd, system_pwd, encryption

    def get_system_user(self):
        """
        get system_user from terminal
        Returns:
            system_user
        """
        print('\nStart Config system_user!')
        tip = "Do you want to config system_user? (y/n): "
        if self.get_confirm(tip) == '-1':
            return '-1'
        while True:
            system_user = self.python.input_info("Enter system_user:\n")
            system_user = system_user.strip() if system_user else ''
            if not system_user or (system_user and '"' not in system_user):
                return system_user

    def get_user_info(self):
        """
        get user_info, which conclude system_user, ip, system_pwd, system_pwd_encryption and raw_pwd
        Returns:
            user_info
        """
        user_info = dict()
        ip_value = self.get_ip()
        if ip_value != '-1':
            user_info['ip'] = ip_value
        system_user = self.get_system_user()
        if system_user != '-1':
            user_info['system_user'] = system_user
        raw_pwd, system_pwd, encryption = self.get_system_pwd()
        if raw_pwd == '-1':
            return user_info
        user_info['system_pwd'] = system_pwd
        user_info['raw_pwd'] = raw_pwd
        user_info['encryption'] = 0 if encryption == 'n' else 1
        return user_info

    def get_command(self):
        """
        get scan/reinforce/rollback command from terminal
        Returns:
            Boolean value: Scan 1; Reinforce 2; Rollback 3
        """
        print("\nStart Config command!")
        tip = "Do you want to config command? (y/n): "
        if self.get_confirm(tip) == '-1':
            return '-1'
        while True:
            command = self.python.input_info("Enter Scan-1/Reinforce-2/Rollback-3 command:(1/2/3):\n")
            command = command.strip() if command else None
            if command and command in ['1', '2', '3']:
                return command

    def get_version(self):
        """
        get scan/reinforce/rollback command from terminal
        Returns:
            Boolean value: Scan 1; Reinforce 2; Rollback 3
        """
        print("\nStart Config version!")
        tip = "Do you want to config version? (y/n): "
        if self.get_confirm(tip) == '-1':
            return '-1'
        while True:
            version = self.python.input_info("Enter Config version:(Format: 1.0/2.0/3.0/...):\n")
            version = version.strip() if version else None
            if version:
                return version

    def get_plugin_timeout(self):
        """
        get scan/reinforce/rollback command from terminal
        Returns:
            Boolean value: Scan 1; Reinforce 2; Rollback 3
        """
        print("\nStart Config plugin timeout!")
        tip = "Do you want to config plugin timeout(default plugin_timeout: 10s)? (y/n): "
        if self.get_confirm(tip) == '-1':
            return '-1'
        while True:
            plugin_timeout = self.python.input_info(
                "Enter Config plugin_timeout(default plugin_timeout: 10s), plugin_timeout must be (0, 3600]:\n")
            plugin_timeout = plugin_timeout.strip() if plugin_timeout else None
            if not plugin_timeout:
                return 10
            regex = r'[0-9]+$'
            pattern = re.compile(regex)
            if pattern.match(plugin_timeout) and 0 < int(plugin_timeout) <= 3600:
                return plugin_timeout

    def get_system_pwd_encryption(self):
        """
        get system password encryption from terminal
        Returns:
            Boolean value: 0 no encryption, 1 with encryption
        """
        print('\nStart Config system_pwd_encryption!')
        while True:
            is_encryption = self.python.input_info("Enter whether system_pwd use encryption (y/n):")
            is_encryption = is_encryption.strip().lower() if is_encryption else None
            if is_encryption and is_encryption in ['y', 'n']:
                return is_encryption
